/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, AstTreeUtils, ts } from 'arkanalyzer/lib';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects, IssueReport } from '../../model/Defects';
import { MatcherCallback, MatcherTypes, FileMatcher } from '../../matcher/Matchers';
import { Rule } from '../../model/Rule';
import { RuleListUtil } from '../../utils/common/DefectsList';
import { RuleFix } from '../../model/Fix';

interface Issue {
    line: number;
    column: number;
    columnEnd: number;
    message: string;
    filePath: string;
    fixCode?: string;
}

interface MessageInfo {
    fixCode: string;
    messageId: MessageId
}

type Options = [{
    ignoreArrowShorthand?: boolean;
    ignoreVoidOperator?: boolean;
}];

type MessageId =
    | 'invalidVoidExpr'
    | 'invalidVoidExprArrow'
    | 'invalidVoidExprArrowWrapVoid'
    | 'invalidVoidExprReturn'
    | 'invalidVoidExprReturnLast'
    | 'invalidVoidExprReturnWrapVoid'
    | 'invalidVoidExprWrapVoid'
    | 'voidExprWrapVoid';

const messages: { [key in MessageId]: string } = {
    invalidVoidExpr: 'Placing a void expression inside another expression is forbidden. Move it to its own statement instead.',
    invalidVoidExprWrapVoid: 'Void expressions used inside another expression must be marked explicitly with the `void` operator.',
    invalidVoidExprArrow: 'Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.',
    invalidVoidExprArrowWrapVoid: 'Void expressions returned from an arrow function shorthand must be marked explicitly with the `void` operator.',
    invalidVoidExprReturn: 'Returning a void expression from a function is forbidden. Please move it before the `return` statement.',
    invalidVoidExprReturnLast: 'Returning a void expression from a function is forbidden. Please remove the `return` statement.',
    invalidVoidExprReturnWrapVoid: 'Void expressions returned from a function must be marked explicitly with the `void` operator.',
    voidExprWrapVoid: 'Mark with an explicit `void` operator.'
};

export class NoConfusingVoidExpressionCheck implements BaseChecker {
    metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: 'docs/no-confusing-void-expression-check.md',
        description: 'Require expressions of type void to appear in statement position'
    };
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private defaultOptions: Options = [{}];
    private config = {
        ignoreArrowShorthand: false,
        ignoreVoidOperator: false
    };
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE
    };
    // 定义需要检测的高风险函数名（可配置）
    private VOID_FUNCTION_PATTERNS = new Set([
        'console.log', 'console.error', 'alert', 'setTimeout',
        'dispatchEvent', 'history.pushState', 'Array.forEach',
        'Promise.then', 'Array.map', 'console.error', 'forEach'
    ]);

    private readonly INVALID_ANCESTOR_TYPES = [
        ts.SyntaxKind.ArrowFunction,
        ts.SyntaxKind.ArrayLiteralExpression,
        ts.SyntaxKind.ObjectLiteralExpression,
        ts.SyntaxKind.VariableDeclaration,
        ts.SyntaxKind.PropertyAssignment,
        ts.SyntaxKind.CallExpression,
        ts.SyntaxKind.VoidExpression
    ];

    public registerMatchers(): MatcherCallback[] {
        const fileMatcher: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check,
        };
        return [fileMatcher];
    };

    public check = (target: ArkFile): void => {
        this.defaultOptions = this.rule && this.rule.option[0] ? this.rule.option as Options : this.defaultOptions;
        this.config.ignoreArrowShorthand = this.defaultOptions[0].ignoreArrowShorthand ?? false;
        this.config.ignoreVoidOperator = this.defaultOptions[0].ignoreVoidOperator ?? false;
        if (target instanceof ArkFile) {
            let code = target.getCode();
            this.checkVoidExpressions(code, target);
        };
    };

    private getFunctionName = (node: ts.CallExpression): string => {
        const expr = node.expression;
        if (ts.isPropertyAccessExpression(expr)) {
            const objName = expr.expression.getText();
            const funcName = expr.name.text;
            return `${objName}.${funcName}`;
        };
        return expr.getText(); // 直接函数名
    };

    private checkVoidExpressions(code: string, arkFile: ArkFile): void {
        const methodAst = AstTreeUtils.getASTNode(arkFile.getName(), code);
        const checkNode = (node: ts.Node): void => {
            // 检测显式 void 操作符
            if (ts.isVoidExpression(node)) {
                this.checkInvalidContext(node, arkFile);
            };
            if (ts.isCallExpression(node)) {
                checkCallExpression(node);
            };
            // 检测箭头函数简写返回
            if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
                checkArrowFunction(node);
            };
            ts.forEachChild(node, checkNode);
        };
        const checkArrowFunction = (node: ts.ArrowFunction): void => {
            const body = node.body;
            const isVoidBody = ts.isCallExpression(body)
                ? this.VOID_FUNCTION_PATTERNS.has(this.getFunctionName(body))
                : ts.isVoidExpression(body);
            if (isVoidBody) {
                this.checkInvalidContext(body, arkFile);
            };
        };
        const checkCallExpression = (node: ts.CallExpression): void => {
            const funcName = this.getFunctionName(node);
            if (this.VOID_FUNCTION_PATTERNS.has(funcName) || funcName.includes('.forEach')) {
                this.checkInvalidContext(node, arkFile);
                checkArguments(node);
            };
        };
        const checkArguments = (node: ts.CallExpression): void => {
            node.arguments.forEach(arg => {
                if (ts.isFunctionLike(arg)) {
                    ts.forEachChild(arg, checkNode);
                };
            });
        };
        checkNode(methodAst);
    };

    // 检查上下文是否非法
    private checkInvalidContext = (node: ts.Node, arkFile: ArkFile): void => {
        if (ts.isVoidExpression(node.parent)) {
            if (this.config.ignoreVoidOperator) {
                return;
            };
        };
        const invalidAncestor = this.findInvalidAncestor(node);
        if (!invalidAncestor) {
            return;
        };
        // 新增：当配置忽略箭头简写且祖先为箭头函数时跳过检测
        if (ts.isArrowFunction(invalidAncestor) && this.config.ignoreArrowShorthand) {
            return;
        };
        if (ts.isArrowFunction(invalidAncestor) && this.config.ignoreVoidOperator) {
            if (this.config.ignoreVoidOperator) {
                return;
            };
        };
        this.createIssue(node, invalidAncestor, arkFile);
    };

    private generateFixCode(node: ts.Node, invalidAncestor: ts.Node, nodeText: string): MessageInfo {
        let fixCode: string | undefined;
        let messageId: MessageId = 'invalidVoidExpr';
        if (ts.isArrowFunction(invalidAncestor)) {
            const arrowFunctionText = invalidAncestor.getText();
            const arrowIndex = arrowFunctionText.indexOf('=>');
            const params = arrowFunctionText.substring(0, arrowIndex + 2).trim();
            const body = arrowFunctionText.substring(arrowIndex + 2).trim();
            fixCode = this.config.ignoreVoidOperator
                ? `void ${body}`
                : `${params} { ${body}; }`;
            messageId = this.config.ignoreVoidOperator ? 'invalidVoidExprArrowWrapVoid' : 'invalidVoidExprArrow';
        } else if (ts.isReturnStatement(invalidAncestor)) {
            fixCode = this.generateReturnStatement(invalidAncestor as ts.ReturnStatement, nodeText).fixCode;
            messageId = this.generateReturnStatement(invalidAncestor as ts.ReturnStatement, nodeText).messageId;
        } else if (this.config.ignoreVoidOperator) {
            // 处理 ignoreVoidOperator 的情况
            if (ts.isBinaryExpression(invalidAncestor)) {
                const operator = invalidAncestor.operatorToken.getText();
                if (operator === '??') {
                    messageId = 'invalidVoidExprWrapVoid';
                    fixCode = `${invalidAncestor.left.getText()} ?? void ${nodeText}`;
                } else if (operator === '||' || operator === '&&') {
                    messageId = 'invalidVoidExprWrapVoid';
                    fixCode = `${invalidAncestor.left.getText()} ${operator} void ${nodeText}`;
                };
            } else if (ts.isPrefixUnaryExpression(invalidAncestor)) {
                messageId = 'invalidVoidExprWrapVoid';
                fixCode = `${invalidAncestor.operator}void ${nodeText}`;
            } else if (ts.isConditionalExpression(invalidAncestor)) {
                messageId = 'invalidVoidExprWrapVoid';
                fixCode = `void ${nodeText} ? ${invalidAncestor.whenTrue.getText()} : ${invalidAncestor.whenFalse.getText()}`;
            } else {
                messageId = 'invalidVoidExprWrapVoid';
                fixCode = `void ${nodeText}`;
            };
        } else {
            messageId = 'invalidVoidExpr';
            fixCode = `${nodeText};`;
            if (invalidAncestor.kind === ts.SyntaxKind.VariableDeclaration &&
                invalidAncestor.parent.kind === ts.SyntaxKind.VariableDeclarationList &&
                this.findSpecialStatement(invalidAncestor.getText())) {
                messageId = 'invalidVoidExprReturnLast';
            };
        };
        return { fixCode: `${fixCode};`, messageId: messageId };
    };

    private generateReturnStatement(invalidAncestor: ts.ReturnStatement, nodeText: string): MessageInfo {
        let fixCode: string | undefined;
        let messageId: MessageId = 'invalidVoidExpr';
        const functionBody = invalidAncestor.parent;
        if (ts.isBlock(functionBody)) {
            const statements = functionBody.statements;
            const currentIndex = statements.indexOf(invalidAncestor);
            const beforeStatements = statements.slice(0, currentIndex).map(s => s.getText()).join('\n');
            const afterStatements = statements.slice(currentIndex + 1).map(s => s.getText()).join('\n');
            if (this.findSpecialReturnStatement(invalidAncestor.getText())) {
                messageId = this.config.ignoreVoidOperator ? 'invalidVoidExprWrapVoid' : 'invalidVoidExpr';
                fixCode = this.config.ignoreVoidOperator ? `void ${nodeText}` : `${nodeText};`;
            } else {
                messageId = this.config.ignoreVoidOperator ? 'invalidVoidExprReturnWrapVoid' : 'invalidVoidExprReturnLast';
                fixCode = this.statementsFixCode(fixCode, nodeText, currentIndex, statements.length, beforeStatements, afterStatements);
            };
        } else {
            messageId = this.config.ignoreVoidOperator ? 'invalidVoidExprReturnWrapVoid' : 'invalidVoidExprReturnLast';
            fixCode = this.config.ignoreVoidOperator ? `void ${nodeText}` : `${nodeText};`;
        };
        return { fixCode: `${fixCode};`, messageId: messageId };
    };

    private statementsFixCode(code: string | undefined, nodeText: string, currentIndex: number,
        statements: number, beforeStatements: string, afterStatements: string): string {
        let fixCode = code;
        if (this.config.ignoreVoidOperator) {
            fixCode = `void ${nodeText}`;
        } else {
            // 处理最后一个语句的情况
            if (currentIndex === statements - 1) {
                fixCode = `${beforeStatements}\n${nodeText};`;
            } else {
                fixCode = `${beforeStatements}\n${nodeText};\n${afterStatements};return`;
            };
        };
        return fixCode;
    };

    private createIssue(node: ts.Node, invalidAncestor: ts.Node, arkFile: ArkFile): void {
        const sourceFile = node.getSourceFile();
        const start = node.getStart();
        const { line, character } = sourceFile.getLineAndCharacterOfPosition(start);
        const nodeText = sourceFile.text.substring(start, node.getEnd());
        const { fixCode, messageId } = this.generateFixCode(node, invalidAncestor, nodeText);
        const resultIssue: Issue = {
            line: line + 1,
            column: character + 1,
            columnEnd: character + nodeText.length + 1,
            message: messages[messageId],
            filePath: arkFile.getFilePath() ?? '',
            fixCode: fixCode
        };
        let ruleFix;
        if (ts.isArrowFunction(invalidAncestor) || ts.isReturnStatement(invalidAncestor)) {
            ruleFix = fixCode ? this.createFix(start, invalidAncestor.getEnd(), fixCode) : undefined;
        };
        this.addIssueReport(resultIssue, ruleFix);
    };

    private createFix(line: number, column: number, code: string): RuleFix {
        return { range: [line, column], text: code };
    };

    private addIssueReport(issue: Issue, ruleFix?: RuleFix): void {
        this.metaData.description = issue.message;
        const severity = this.rule.alert ?? this.metaData.severity;
        const defects = new Defects(issue.line, issue.column, issue.columnEnd, this.metaData.description, severity, this.rule.ruleId, issue.filePath,
            this.metaData.ruleDocPath, true, false, (ruleFix !== undefined ? true : false));
        this.issues.push(new IssueReport(defects, ruleFix));
        RuleListUtil.push(defects);
    };

    private handleLogical(
        parent: ts.Node,
        node: ts.Node,
        isExplicitVoid: (n: ts.Node) => boolean
    ): ts.Node | null | undefined {
        const binaryParent = parent as ts.BinaryExpression;
        // 完全复制原始代码逻辑
        if (this.config.ignoreVoidOperator) {
            if (this.isWrappedByVoid(binaryParent)) {
                return null;
            };
            if (binaryParent.operatorToken.kind === ts.SyntaxKind.BarBarToken &&
                binaryParent.right === node &&
                !ts.isVoidExpression(node)) {
                return binaryParent;
            };
            return isExplicitVoid(node) ? null : binaryParent;
        };
        // 严格保留原始嵌套检查逻辑
        let isNested = false;
        let ancestor = binaryParent.parent;
        while (ancestor) {
            if (ts.isBinaryExpression(ancestor) && this.isLogicalOperator(ancestor)) {
                isNested = true;
                break;
            };
            ancestor = ancestor.parent;
        };
        if (isNested || !ts.isExpressionStatement(binaryParent.parent)) {
            let currentParent: ts.Node | null = binaryParent.parent;
            while (currentParent) {
                if (this.INVALID_ANCESTOR_TYPES.includes(currentParent.kind)) {
                    return currentParent;
                };
                currentParent = currentParent.parent;
            };
            return binaryParent;
        };
        return !ts.isExpressionStatement(binaryParent.parent)
            ? binaryParent
            : undefined;
    };

    private isLogicalOperator(node: ts.BinaryExpression): boolean {
        return node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken;
    };

    private checkReturnStatement(parent: ts.Node, node: ts.Node, isExplicitVoid: (n: ts.Node) => boolean): ts.Node | null | undefined {
        if (!ts.isReturnStatement(parent)) {
            return undefined;
        };
        // 完全保留原始处理逻辑
        const returnExpr = parent.expression;
        if (returnExpr && ts.isBinaryExpression(returnExpr) && returnExpr.operatorToken.kind === ts.SyntaxKind.CommaToken) {
            let current: ts.Node = node;
            while (current.parent !== returnExpr) {
                current = current.parent!;
            };
            if (current === returnExpr.left) {
                return null;
            };
        };
        return this.config.ignoreVoidOperator
            ? (isExplicitVoid(node) ? null : parent)
            : parent;
    };

    private isWrappedByVoid(n: ts.Node): boolean {
        let current: ts.Node | null = n;
        while (current) {
            if (ts.isVoidExpression(current)) {
                return true;
            };
            current = current.parent;
        };
        return false;
    };

    private isWrappedByExplicitVoid = (n: ts.Node): boolean => ts.isVoidExpression(n);
    // 检查节点是否被显式 void 包裹（自身或任何祖先）
    private isExplicitVoid = (n: ts.Node): boolean => {
        let current: ts.Node | null = n;
        while (current) {
            if (ts.isVoidExpression(current)) {
                return true;
            };
            current = current.parent;
        };
        return false;
    };
    // 4. 查找非法祖先节点（与之前逻辑类似，但不再依赖类型）
    private findInvalidAncestor(node: ts.Node): ts.Node | null {
        let parent = node.parent;
        while (parent) {
            if (this.isVoidExpressionWithZero(node, parent)) {
                return null;
            };
            if (ts.isReturnStatement(parent)) {
                const result = this.checkReturnStatement(parent, node, this.isExplicitVoid);
                if (result !== undefined) {
                    return result;
                };
            };
            if (this.isCoalesceExpression(parent, node)) {
                return parent;
            };
            if (this.isLogicalExpression(parent, node)) {
                const result = this.handleLogical(parent, node, this.isExplicitVoid);
                if (result !== undefined) {
                    return result;
                };
            };
            if (ts.isConditionalExpression(parent)) {
                if (!ts.isConditionalExpression(parent)) {
                    return null;
                };
                if (this.config.ignoreVoidOperator) {
                    const bothVoid = ts.isVoidExpression(parent.whenTrue) && ts.isVoidExpression(parent.whenFalse);
                    return bothVoid ? null : (this.isExplicitVoid(parent) ? null : parent);
                };
                const result = parent.condition === node ? parent : undefined;
                if (result !== undefined) {
                    return result;
                };
            };
            if (ts.isParenthesizedExpression(parent)) {
                parent = parent.parent;
                continue;
            };
            if (ts.isPrefixUnaryExpression(parent)) {
                return this.handlePrefixUnaryExpression(parent, node);
            };
            if (ts.isExpressionStatement(parent)) {
                return null;
            };
            if (this.isInvalidAncestorType(parent)) {
                return this.handleInvalidAncestorType(parent);
            };
            parent = parent.parent;
        };
        return null;
    };

    private isVoidExpressionWithZero(node: ts.Node, parent: ts.Node): boolean {
        return ts.isVoidExpression(node) && node.expression.getText() === '0' && ts.isVariableDeclaration(parent);
    };

    private isCoalesceExpression(parent: ts.Node, node: ts.Node): boolean {
        return ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken &&
            this.config.ignoreVoidOperator && parent.right === node && !this.isExplicitVoid(node) && !this.isWrappedByExplicitVoid(parent);
    };

    private isLogicalExpression(parent: ts.Node, node: ts.Node): boolean {
        return ts.isBinaryExpression(parent) && this.isLogicalOperator(parent);
    };

    private handlePrefixUnaryExpression(parent: ts.Node, node: ts.Node): ts.Node | null {
        if (this.config.ignoreVoidOperator) {
            return this.isExplicitVoid(node) ? null : parent;
        };
        return parent;
    };

    private isInvalidAncestorType(parent: ts.Node): boolean {
        return this.INVALID_ANCESTOR_TYPES.includes(parent.kind);
    };

    private handleInvalidAncestorType(parent: ts.Node): ts.Node | null {
        if (this.config.ignoreVoidOperator && ts.isVariableDeclaration(parent)) {
            return null;
        };
        return parent;
    };

    private findSpecialStatement(code: string): boolean {
        if (!code) {
            return false;
        };
        const regex = /(\w+)\s*=\s*function\s*\(\s*\)\s*{\s*let\s+(\w+)\s*=\s*([^;]+);\s*return\s*\2\s*\|\|\s*console\./g;
        if (regex.test(code)) {
            return true;
        };
        return false;
    };

    private findSpecialReturnStatement(code: string): boolean {
        if (!code) {
            return false;
        };
        const regex = /^\s*return\s+[a-zA-Z_$][a-zA-Z0-9_$]*\s*,\s*console\.log\(\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\)\s*;?\s*$/g;
        if (regex.test(code)) {
            return true;
        };
        return false;
    };
}